/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * Validated Data Container Class
 *      Provides a class for storing a set of data with hash code and CRC-32 values.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Security.Cryptography;

namespace Adaptive.Foundation.IO
{
    /// <summary>
    /// Provides a class for storing a set of data with hash code and CRC-32 values.
    /// </summary>
    public class ValidatedDataContainer : DisposableObjectBase
    {
        /*--------------------------------------------------------------------------------
         * Private Member Declarations
         *------------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// Data content storage.
        /// </summary>
        private byte[] _dataContent;
        /// <summary>
        /// Hash of the data.
        /// </summary>
        private int _hashCode;
        /// <summary>
        /// CRC-32 value.
        /// </summary>
        private int _crcValue;
        /// <summary>
        /// Ordinal index.
        /// </summary>
        private int _index;
        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
         *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        public ValidatedDataContainer()
            : base()
        {
        }
        /// <summary>
        /// Initializes a new instance of the clas to the specified
        /// data content.
        /// </summary>
        /// <param name="data">
        /// The data being represented.
        /// </param>
        public ValidatedDataContainer(byte[] data)
        {
            _dataContent = data;
            CalculateHash();
            CalculateCrc();
        }
        /// <summary>
        /// Initializes a new instance of the class with the 
        /// data loaded from the related stream.
        /// </summary>
        /// <param name="reader">
        /// A <see cref="BinaryReader"/> instance used to read the 
        /// data from an open stream.
        /// </param>
        public ValidatedDataContainer(BinaryReader reader)
        {
            LoadFromReader(reader);
        }
        /// <summary>
        /// Deallocates internally allocated objects.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating whether the object is being disposed.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                if (_dataContent != null)
                    Array.Clear(_dataContent, 0, _dataContent.Length);
            }

            _dataContent = null;
            base.Dispose(disposing);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Properties
         *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets or sets the data content being represented.
        /// </summary>
        /// <value>
        /// A byte array containing the data.
        /// </value>
        /// <remarks>
        /// Setting this property causes the hash code and CRC to be
        /// recalculated.
        /// </remarks>
        public byte[] Data
        {
            get { return _dataContent; }
            set
            {
                _dataContent = value;
                CalculateHash();
                CalculateCrc();
            }
        }
        /// <summary>
        /// Gets or sets the ordinal/sequential index of the instance.
        /// </summary>
        /// <value>
        /// The integer value indicating the order of the data.
        /// </value>
        public int Index
        {
            get{return _index;}
            set{_index = value;}
        }
        /// <summary>
        /// Gets a unique ID string for referenceing the object instance.
        /// </summary>
        /// <value>
        /// An ID value for use as a rererence key value.
        /// </value>
        public string ID
        {
            get { return Conversion.ToHexadecimal(Conversion.ToBytes(_hashCode + _crcValue + _index)); }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Clears the contents of the object instance without disposing of the instance.
        /// </summary>
        public void Clear()
        {
            _dataContent = null;
            _crcValue = 0;
            _hashCode = 0;
        }
        /// <summary>
        /// Loads the content of the object from the current open stream.
        /// </summary>
        /// <param name="sourceData">
        /// An open <see cref="Stream"/> instance to read from.
        /// </param>
        /// <returns>
        /// The size of the data content read from the stream.
        /// </returns>
        public int LoadFromStream(Stream sourceData)
        {
            BinaryReader reader = null;         //Data reader.
            int returnValue = 0;                //Return value.

            if (sourceData.CanRead)
            {
                reader = new BinaryReader(sourceData);
                returnValue = LoadFromReader(reader);
                reader=null;
            }
            return returnValue;
        }
        /// <summary>
        /// Loads the content of the object from the current open stream.
        /// </summary>
        /// <param name="reader">
        /// An open <see cref="BinaryReader"/> used to read data from
        /// the open stream.
        /// </param>
        /// <returns>
        /// The size of the data content read from the stream.
        /// </returns>
        public int LoadFromReader(BinaryReader reader)
        {
            int dataSize = 0;                       //Size of the data block.
            int hashCode = 0;                       //Hash code in source stream.
            int crc = 0;                            //CRC value in source stream.
            byte[] content = null;                  //Data read from stream.

            //Data Format must be as follows:
            //
            // Int32    - Size of data block.
            // Byte[]   - Byte array of specified size. 
            // Int32    - Hash code.
            // Int32    - CRC value.
            
            dataSize = ReadInt32(reader);
            content = ReadBytes(reader, dataSize);
            hashCode = ReadInt32(reader);
            crc = ReadInt32(reader);
            
            //Set the data and compare the values.
            Data = content;

            //If the hash comparison fails, throw the appropriate exception.
            if (_hashCode != hashCode)
                throw new AiHashCodeFailedException();

            //If the CRC comparison fails, throw the appropriate exception.
            if (_crcValue != crc)
                throw new AiCrcFailedException();

            //Otherwise, return the appropriate data.
            return _dataContent.Length;
        }
        /// <summary>
        /// Attempts to write the contents of the object to the open stream.
        /// </summary>
        /// <param name="sourceData">
        /// A <see cref="Stream"/> instance used to store the data being written.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>writer</i> parameter is <b>null</b>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Occurs when the underlying stream for <i>writer</i> is not writable.
        /// </exception>
        public void WriteToStream(Stream sourceData)
        {
            BinaryWriter writer = null;         //Data writer.

            if (sourceData != null)
                writer = new BinaryWriter(sourceData);
            WriteToStream(writer);
            writer = null;
        }
        /// <summary>
        /// Attempts to write the contents of the object to the open stream.
        /// </summary>
        /// <param name="writer">
        /// A <see cref="BinaryWriter"/> instance used to write the data to the open stream.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Occurs when the <i>writer</i> parameter is <b>null</b>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Occurs when the underlying stream for <i>writer</i> is not writable.
        /// </exception>
        public void WriteToStream(BinaryWriter writer)
        {
            
            if (writer == null)
                throw new ArgumentNullException("writer");

            if (!writer.BaseStream.CanWrite)
                throw new ArgumentOutOfRangeException("writer", "Underlying base stream cannot be written to.");

            //Data Format must be as follows:
            //
            // Int32    - Size of data block.
            // Byte[]   - Byte array of specified size. 
            // Int32    - Hash code.
            // Int32    - CRC value.
            writer.Write(_dataContent.Length);
            writer.Write(_dataContent);
            writer.Write(_hashCode);
            writer.Write(_crcValue);
            writer.Flush();
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Calculates a hash code for the current data content.
        /// </summary>
        private void CalculateHash()
        {
            MemoryStream container = null;                  //Result container.
            BinaryWriter writer = null;                     //Result writer.
            BinaryReader reader = null;                     //Result reader.
            MD5CryptoServiceProvider provider = null;       //Hash generator.

            if (_dataContent != null)
            {
                //Create objects.
                container = new MemoryStream();
                writer = new BinaryWriter(container);
                reader = new BinaryReader(container);
                provider = new MD5CryptoServiceProvider();

                try
                {
                    //Create the hash bytes and store them.
                    writer.Write(provider.ComputeHash(_dataContent));
                    writer.Flush();
                }
                catch (Exception)
                {
                    //Clear items.
                    reader.Close();
                    writer.Close();
                    container.Close();
                    container.Dispose();
                    container = null;
                    _hashCode = 0;
                }

                //Translate the hash value to an integer.
                if (container != null)
                {
                    //Re-wind and read.
                    container.Seek(0, SeekOrigin.Begin);
                    _hashCode = reader.ReadInt32();

                    writer.Close();
                    reader.Close();
                    container.Close();
                    container.Dispose();
                }
            }
            else
                _hashCode = 0;

        }
        /// <summary>
        /// Calculates a CRC-32 value for the current data content.
        /// </summary>
        private void CalculateCrc()
        {
            Crc32 crcGenerator = null;              //CRC generator.

            if (_dataContent != null)
            {
                crcGenerator = new Crc32();
                try
                {
                    crcGenerator.Update(_dataContent);
                    _crcValue = (int)crcGenerator.Value;
                }
                catch
                {
                    _crcValue = 0;
                }
            }
            else
                _crcValue = 0;
        }
        /// <summary>
        /// Attempts to read an integer from an open stream.
        /// </summary>
        /// <param name="reader">
        /// An open <see cref="BinaryReader"/> instance used to read data from an open stream.
        /// </param>
        /// <returns>
        /// The value read from the stram.
        /// </returns>
        /// <exception cref="AiDataFormatException">
        /// Occurs when an exception is encountered while reading the integer.  This assumes the
        /// underlying stream is in the correct format.
        /// </exception>
        private int ReadInt32(BinaryReader reader)
        {
            int returnValue = 0;                //Return value.

            try
            {
                returnValue = reader.ReadInt32();
            }
            catch (Exception)
            {
                throw new AiDataFormatException();
            }
            return returnValue;
        }
        /// <summary>
        /// Attempts to read the specified number of bytes from an open stream.
        /// </summary>
        /// <param name="reader">
        /// An open <see cref="BinaryReader"/> instance used to read data from an open stream.
        /// </param>
        /// <param name="size">
        /// The number of bytes to be read.
        /// </param>
        /// <returns>
        /// A byte array containing the values read from the stream.
        /// </returns>
        /// <exception cref="AiDataFormatException">
        /// Occurs when an exception is encountered while reading the integer.  This assumes the
        /// underlying stream is in the correct format.
        /// </exception>
        private byte[] ReadBytes(BinaryReader reader, int size)
        {
            byte[] returnValue = null;          //Return value.

            try
            {
                returnValue = reader.ReadBytes(size);
            }
            catch (Exception)
            {
                throw new AiDataFormatException();
            }
            return returnValue;
        }
        #endregion
    }
}
